package command

import (
	"fmt"
	_ "github.com/logoove/sqlite"
	"os"
	_ "path"
	"redis-check/client"
	"redis-check/common"
	"redis-check/conf"
	"redis-check/handle"
	"redis-check/tool"
	"strings"
)

const (
	NX = "nx"
	EX = "ex"
	BK = "bk"
)

type PreviewOptions struct {
	common.BaseOptions
	Source     conf.RedisArgs `group:"source" description:"源Redis配置信息" namespace:"s"`
	Target     conf.RedisArgs `group:"target" description:"目的Redis配置信息" namespace:"t"`
	Match      string         `long:"match" default:"" description:"源Redis扫描Key的匹配模式，未指定时扫描所有Key"`
	Batch      int            `long:"batch" default:"256" description:"批量处理大小，值范围[1, 10000]"`
	Qps        int            `long:"qps" default:"1000" description:"最大处理QPS，，如果qps=10，则获取处理速度为 10 * batch每秒"`
	KeyList    string         `long:"key-list" default:"" description:"指定Key列表，例如: 'abc|efg|dkg'"`
	FilterOpts string         `long:"filter-opts" default:"" description:"过滤选项，nx-在目标Redis不存在, ex-在目标Redis存在, bk-大Key"`
	BigKey     int64          `long:"big-key" default:"5120"  description:"大Key的阀值"`
}

type PreviewCommand struct {
	Options      *PreviewOptions
	SourceHost   client.RedisHost
	TargetHost   client.RedisHost
	filterOpts   []string
	operation    string
	all          bool
	targetClient *client.RedisClient
}

func (p *PreviewCommand) Execute(args []string) error {
	ret, err := common.RunWithArgs(p.Options, os.Args[2:])
	if !ret {
		return err
	}
	p.SourceHost = p.Options.Source.ToRedisHost("source", true)
	p.filterOpts = common.RegSplit(p.Options.FilterOpts, "\\s*[,;|]+\\s*")
	if common.IndexOfStrings(p.filterOpts, NX) >= 0 || common.IndexOfStrings(p.filterOpts, EX) >= 0 {
		p.TargetHost = p.Options.Source.ToRedisHost("target", true)
	}
	redisScanner := handle.RedisScanner{
		Host:          p.SourceHost,
		BatchCount:    p.Options.Batch,
		MatchPattern:  p.Options.Match,
		KeyList:       common.RegSplit(p.Options.KeyList, "|"),
		HandleThreads: 1,
		Qps:           p.Options.Qps,
	}
	redisScanner.StartScanRedis(func(context *handle.ScanState) {
		if context.Run && len(p.TargetHost.Addr) > 0 {
			p.targetClient = tool.RedisConnectDB(p.TargetHost, context.CurrDb)
		} else if !context.Run && p.targetClient != nil {
			p.targetClient.Close()
		}
	}, redisScanner.ScanPhysicalDB, p.HandlePreview)
	common.Logger.Infof("--------------- 处理完成 ----------------")
	return nil
}

func (p *PreviewCommand) hasAnyOpts(opts ...string) bool {
	if len(opts) == 0 {
		return false
	}
	for _, opt := range opts {
		if common.IndexOfStrings(p.filterOpts, opt) >= 0 {
			return true
		}
	}
	return false
}

func (p *PreviewCommand) HandlePreview(sourceClient *client.RedisClient, db int32, keyInfo []*common.Key) {
	keyLen := len(keyInfo)
	if keyLen <= 0 {
		return
	}
	bk, nx, ex := p.hasAnyOpts(BK), p.hasAnyOpts(NX), p.hasAnyOpts(EX)
	if bk {
		tool.FetchTypeAndLen(keyInfo, sourceClient, nil)
	}
	if nx || ex {
		tool.FetchKeyExists(keyInfo, p.targetClient, false)
	}
	if bk || nx || ex {
		filterKeyInfo := make([]*common.Key, 0, len(keyInfo))
		for i := 0; i < len(keyInfo); i++ {
			if ex && !keyInfo[i].TargetAttr.Exists {
				continue
			} else if nx && keyInfo[i].TargetAttr.Exists {
				continue
			} else if bk && keyInfo[i].SourceAttr.ItemCount < p.Options.BigKey {
				continue
			}
			filterKeyInfo = append(filterKeyInfo, keyInfo[i])
		}
		p.PreviewKeyInfo(filterKeyInfo, sourceClient)
	} else {
		p.PreviewKeyInfo(keyInfo, sourceClient)
	}
}

func (p *PreviewCommand) PreviewKeyInfo(keyInfo []*common.Key, sourceClient *client.RedisClient) {
	if len(keyInfo) <= 0 {
		return
	}
	fmt.Fprintf(os.Stdout, "扫描到 %v 个Key:\n", len(keyInfo))
	options := []string{"continue", "continueAll", "deleteKey", "deleteAll"}
	if p.targetClient != nil {
		options = append(options, "copyKey", "copyAll", "replaceKey", "replaceAll")
	}
	var operation string
	var keys []string
	for len(keyInfo) > 0 {
		newKeyInfo := keyInfo
		if p.all {
			operation = p.operation
			if operation == "continue" {
				operation = "show"
			}
			keyInfo = make([]*common.Key, 0)
		} else {
			p.ShowSourceKeys(keyInfo)
			operation, keys = p.ScanOperation(options)
			newKeyInfo = p.FilterKeys(keyInfo, keys, true)
			if len(newKeyInfo) <= 0 {
				fmt.Fprintf(os.Stdout, "未找到匹配的Key: %v\n", keys)
				continue
			} else if len(keys) > 0 {
				keyInfo = p.FilterKeys(keyInfo, keys, false)
			} else {
				keyInfo = make([]*common.Key, 0)
			}
		}
		if operation == "delete" {
			p.DeleteSourceKeys(newKeyInfo, sourceClient)
		} else if operation == "show" {
			p.ShowSourceKeys(newKeyInfo)
		} else if operation == "copy" {
			p.CopySourceKeys(newKeyInfo, sourceClient, p.targetClient, false)
		} else if operation == "replace" {
			p.CopySourceKeys(newKeyInfo, sourceClient, p.targetClient, true)
		}
	}
}

func (p *PreviewCommand) FilterKeys(keyInfo []*common.Key, keys []string, included bool) []*common.Key {
	if len(keys) <= 0 {
		return keyInfo
	}
	newKeyInfo := make([]*common.Key, 0, len(keyInfo))
	for i := 0; i < len(keyInfo); i++ {
		if common.IndexOfStrings(keys, string(keyInfo[i].Key)) < 0 {
			if !included {
				newKeyInfo = append(newKeyInfo, keyInfo[i])
			}
		} else if included {
			newKeyInfo = append(newKeyInfo, keyInfo[i])
		}
	}
	return newKeyInfo
}

func (p *PreviewCommand) ScanOperation(options []string) (string, []string) {
	operation, keys := common.SelectOption("请选择Key的操作", options[0], options...)
	if strings.HasSuffix(operation, "All") {
		operation = operation[:len(operation)-3]
		keep, _ := common.SelectOption("后续扫描到的Key是否继续按照次方式处理", "false", "false", "true")
		p.all = "true" == keep
	} else if strings.HasSuffix(operation, "Key") {
		operation = operation[:len(operation)-3]
		if len(keys) == 0 {
			keys = common.ScanConf("请输入需要处理的Key", false)
		}
	}
	if p.all {
		p.operation = operation
	}
	return operation, keys
}

func (p *PreviewCommand) ShowSourceKeys(keyInfo []*common.Key) {
	for i := 0; i < len(keyInfo); i++ {
		format := "  %s"
		params := []interface{}{string(keyInfo[i].Key)}
		if keyInfo[i].Tp != common.EndKeyType {
			format += " %s"
			params = append(params, keyInfo[i].Tp.Name)
		}
		if keyInfo[i].SourceAttr.ItemCount > 0 {
			format += " %d"
			params = append(params, keyInfo[i].SourceAttr.ItemCount)
		}
		fmt.Fprintf(os.Stdout, format+"\n", params...)
	}
}
func (p *PreviewCommand) DeleteSourceKeys(keyInfo []*common.Key, sourceClient *client.RedisClient) {
	successCount, errorCount := 0, 0
	for i := 0; i < len(keyInfo); i++ {
		reply, err := sourceClient.Del(keyInfo[i])
		if err != nil {
			errorCount++
			reply = err.Error()
		} else {
			successCount++
		}
		fmt.Fprintf(os.Stdout, "删除Key: %s, 结果: %v\n", string(keyInfo[i].Key), reply)
	}
	fmt.Fprintf(os.Stdout, "删除Key操作完成, 成功: %v, 失败: %v\n\n", successCount, errorCount)
}

func (p *PreviewCommand) CopySourceKeys(keyInfo []*common.Key, sourceClient *client.RedisClient, targetClient *client.RedisClient, replace bool) {
	tool.FetchKeyTypesIfNeed(keyInfo, sourceClient)
	tool.FetchKeyTTL(keyInfo, sourceClient, true)
	counter := common.NewCounter()
	copier := func(key *common.Key, value interface{}) (interface{}, error) {
		if key.Tp == common.StringKeyType {
			counter.Add(1)
			return targetClient.Set(key, value)
		} else if key.Tp == common.HashKeyType {
			counter.Add(len(value.([]interface{})))
			return targetClient.HashSet(key, value.([]interface{}))
		} else if key.Tp == common.SetKeyType {
			counter.Add(len(value.([]interface{})))
			return targetClient.SetAdd(key, value.([]interface{}))
		} else if key.Tp == common.ZsetKeyType {
			counter.Add(len(value.([]interface{})))
			return targetClient.ZsetAdd(key, value.([]interface{}))
		} else if key.Tp == common.ListKeyType {
			counter.Add(len(value.([]interface{})))
			return targetClient.ListPush(key, value.([]interface{}))
		} else {
			return nil, fmt.Errorf("key type %s is not string/hash/set/zset/list", key.Tp)
		}
	}
	procName := common.IfString(replace, "替换Key", "复制Key")
	successCount, errorCount, ignoreCount := 0, 0, 0
	for i := 0; i < len(keyInfo); i++ {
		if keyInfo[i].SourceAttr.Expire >= 0 && keyInfo[i].SourceAttr.Expire <= 60 {
			fmt.Fprintf(os.Stdout, "复制Key: %s, 结果: 放弃复制, 原因: Key将会在 %v 秒后过期 \n", string(keyInfo[i].Key), keyInfo[i].SourceAttr.Expire)
			continue
		}
		procKeyName := procName + "：" + string(keyInfo[i].Key)
		counter.Init(procKeyName)
		if keyInfo[i].Tp != common.StringKeyType && (keyInfo[i].Tp == common.ListKeyType || replace) {
			targetClient.Del(keyInfo[i])
		}
		result, err := sourceClient.ScanValues(keyInfo[i], p.Options.Batch, copier)
		if err != nil {
			errorCount++
			counter.Finish("%s, 结果：失败, 原因: %v", procKeyName, err.Error())
		} else {
			if keyInfo[i].SourceAttr.Expire > 0 && !targetClient.Expire(keyInfo[i], keyInfo[i].SourceAttr.Expire) {
				ignoreCount++
				counter.Finish("%s, 结果：忽略, 原因: 设置Key过期时间失败，时间 %v 秒", procKeyName, keyInfo[i].SourceAttr.Expire)
			} else {
				successCount++
				counter.Finish("%s, 结果：成功, 长度: %v", procKeyName, result)
			}
		}
	}
	fmt.Fprintf(os.Stdout, "%s完成, 成功数量: %v, 失败数量: %v, 忽略数量: %v\n\n", procName, successCount, errorCount, ignoreCount)
}
